/*
 * Copyright (C) 2012 Caldia Apps.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.caldia.android.frameworkG1.controller;

import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.caldia.android.frameworkG1.activity.AbstractActivity;
import org.caldia.android.frameworkG1.application.Application;
import org.caldia.android.frameworkG1.fragment.TopFragment;

import android.content.ComponentName;
import android.content.Intent;
import android.util.Pair;

/**
 * コントローラー親クラス
 *
 * @author k-matsuda@caldia.org
 *
 */
public final class TransitionManager extends ActivityManager
		implements
			ITransitionManager {

	/**
	 * インスタンス
	 */
	private static TransitionManager sInst;

	/**
	 * モードマップ
	 */
	private final ConcurrentHashMap<Integer, String> mModeMap = new ConcurrentHashMap<Integer, String>();

	/**
	 * モードマップ
	 */
	private final ConcurrentHashMap<String, Integer> mModeClsMap = new ConcurrentHashMap<String, Integer>();

	/**
	 * コンストラクタ
	 *
	 * @param context
	 *            コンテキスト
	 */
	private TransitionManager(final Application application, final int logNo) {

		super(application, logNo);

	}

	/**
	 * インスタンス取得
	 *
	 * @param application
	 *            アプリケーション
	 * @param logNo
	 *            トラッキング用番号
	 * @return インスタンス
	 */
	public static synchronized ITransitionManager getInstance(
			final Application application, final int logNo) {

		if (sInst == null) {
			sInst = new TransitionManager(application, logNo);
		}
		return sInst;

	}

	@Override
	public int getNextFragmentNo(final int currentNo, final int logNo) {

		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.getNextFragmentNo(currentNo, logNo);
		}

		return INVALID_VALUE;
	}

	@Override
	public int getPreviousFragmentNo(final int currentNo, final int logNo) {
		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.getPreviousFragmentNo(currentNo, logNo);
		}

		return INVALID_VALUE;
	}

	@Override
	public int getNextActivityNo(final int currentNo, final int logNo) {
		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.getNextActivityNo(currentNo, logNo);
		}

		return INVALID_VALUE;
	}

	@Override
	public int getPreviousNo(final int currentNo, final int logNo) {
		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.getPreviousNo(currentNo, logNo);
		}

		return INVALID_VALUE;
	}

	@Override
	public void startMode(final int mode, final ControllerListener l,
			final int logNo) {

		// コマンド
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {

				// モード番号保存
				TransitionManager.super.setMode(mode, logNo);

				// モードマネージャ
				final ITransitionManager modeManager = getModeManager(mode,
						logNo);
				if (modeManager != null) {
					modeManager.startMode(mode, l, logNo);

					// 次の画面を起動する
					moveToNextActivity(INVALID_VALUE, l, logNo);

				} else {
					if (l != null) {
						try {
							l.onErrorStartActivity(INVALID_VALUE,
									new IllegalArgumentException("mode " + mode
											+ " is invalid value"));
						} catch (final Exception e) {
							e.printStackTrace();
						}
					}
				}

			}
		};

		// コマンド送信
		getMessagingController(logNo).putCommand("startMode", runnable, l);

	}

	@Override
	public void startActivity(final int activityNo, final ControllerListener l,
			final int logNo) {

		// コマンド
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {
				// 現在のモード番号
				final int modeNo = getMode(logNo);
				// モードマネージャ
				final ITransitionManager modeManager = getModeManager(modeNo,
						logNo);
				if (modeManager != null) {
					modeManager.startActivity(activityNo, l, logNo);
					return;
				}

				throw new RuntimeException();
			}
		};

		getMessagingController(logNo).putCommand("startActivity", runnable, l);

	}

	@Override
	public TopFragment getFragment(final int fragmentNo, final int logNo) {
		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.getFragment(fragmentNo, logNo);
		}

		throw new RuntimeException();
	}

	@Override
	public boolean hasNextFragment(final int currentNo, final int logNo) {
		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.hasNextFragment(currentNo, logNo);
		}

		return false;
	}

	@Override
	public boolean hasNextActivity(final int currentNo, final int logNo) {
		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.hasNextActivity(currentNo, logNo);
		}

		return false;
	}

	@Override
	public boolean hasPreviousFragment(final int currentNo, final int logNo) {
		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.hasPreviousFragment(currentNo, logNo);
		}

		throw new RuntimeException();
	}

	@Override
	public boolean hasPreviousActivity(final int currentNo, final int logNo) {
		// 現在のモード番号
		final int modeNo = getMode(logNo);
		// モードマネージャ
		final ITransitionManager modeManager = getModeManager(modeNo, logNo);
		if (modeManager != null) {
			return modeManager.hasPreviousActivity(currentNo, logNo);
		}

		throw new RuntimeException();
	}

	@Override
	public void moveToNextActivity(final int currentNo,
			final ControllerListener l, final int logNo) {

		// コマンド
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {
				// モード番号
				final int modeNo = getMode(logNo);
				// モードマネージャ
				final ITransitionManager modeManager = getModeManager(modeNo,
						logNo);

				if (modeManager != null) {

					// 遷移先画面番号
					final int nextActivityNo = modeManager.getNextActivityNo(
							currentNo, logNo);

					// 遷移先フラグメント番号
					final int nextFragmentNo = modeManager.getNextFragmentNo(
							currentNo, logNo);

					if (INVALID_VALUE < nextActivityNo) {

						// 通常画面起動
						startActivity(nextActivityNo, l, false, logNo);

					} else if (INVALID_VALUE < nextFragmentNo) {

						// トップフラグメントを指定して画面を起動
						startActivity(nextFragmentNo, l, true, logNo);

					} else {
						// 起動失敗
						onErrorStartActivity(INVALID_VALUE,
								new NullPointerException("画面が無い"), l, logNo);
					}
					return;
				}

				throw new RuntimeException("画面起動失敗");

			}
		};

		// コマンド送信
		getMessagingController(logNo).putCommand("moveToNextActivity",
				runnable, l);

	}

	@Override
	public void moveToPreviousActivity(final int currentNo,
			final ControllerListener l, final int logNo) {

		// コマンド
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {
				// モード番号
				final int modeNo = getMode(logNo);
				// モードマネージャ
				final ITransitionManager modeManager = getModeManager(modeNo,
						logNo);

				if (modeManager != null) {

					// 遷移先画面番号
					final int nextActivityNo = modeManager.getPreviousNo(
							currentNo, logNo);

					// 遷移先フラグメント番号
					final int nextFragmentNo = modeManager
							.getPreviousFragmentNo(currentNo, logNo);

					if (INVALID_VALUE < nextActivityNo) {

						// 通常画面起動
						startActivity(nextActivityNo, l, false, logNo);

					} else if (INVALID_VALUE < nextFragmentNo) {

						// トップフラグメントを指定して画面を起動
						startActivity(nextFragmentNo, l, true, logNo);

					} else {

						// 起動失敗
						onErrorStartActivity(INVALID_VALUE,
								new NullPointerException("画面が無い"), l, logNo);
					}

					return;
				}

				throw new RuntimeException();

			}
		};

		// コマンド送信
		getMessagingController(logNo).putCommand("moveToPreviousActivity",
				runnable, l);

	}

	@Override
	public void setNextTrasition(final int currentNo, final int nextNo,
			final int logNo) {
		// TODO : 未実装
		throw new RuntimeException();
	}

	@Override
	public void setPrevioutTrasiton(final int currentNo, final int previousNo,
			final int logNo) {
		// TODO : 未実装
		throw new RuntimeException();
	}

	@Override
	public void addModeManager(final int modeNo,
			final Class<? extends ModeManager> modeManager, final int logNo) {

		// モードクラス名
		final String name = modeManager.getName();

		mModeClsMap.putIfAbsent(name, modeNo);
		mModeMap.putIfAbsent(modeNo, name);

	}

	@Override
	public ModeManager getModeManager(final int modeNo, final int logNo) {

		final String className = mModeMap.get(modeNo);
		if (className != null) {

			try {

				final Class<?> cls = Class.forName(className);
				final Constructor<?> constructor = cls
						.getConstructor(Application.class);
				return (ModeManager) constructor
						.newInstance(getApplication(logNo));

			} catch (final Exception e) {
				e.printStackTrace();
			}
		}

		return null;
	}

	@Override
	public int getModeNo(final Class<? extends ModeManager> modeManager,
			final int logNo) {

		// モード名
		final String name = modeManager.getName();

		// モード番号
		final Integer modeNo = mModeClsMap.get(name);

		if (modeNo != null) {
			return modeNo;
		}

		// モードが登録されていない
		return INVALID_VALUE;
	}

	// ****************************************************************************
	// 過去の遺産

	/**
	 * 次の画面に遷移する
	 *
	 * @param cls
	 *            遷移元画面
	 * @return true 遷移成功
	 */
	// public void moveToNextActivity(final Object cls,
	// final ControllerListener l, final int logNo) {
	// moveToNextActivity(cls, l, false, logNo);
	// }

	// public void moveToNextActivity(final Object cls,
	// final ControllerListener l, final boolean isFragment,
	// final int logNo) {
	//
	// getMessagingController(logNo).putCommand("move to next", new Runnable() {
	//
	// @Override
	// public void run() {
	//
	// // 遷移元画面番号
	// final int current = getActivityNo(cls);
	//
	// // 遷移元画面番号が無い場合でも次の画面がある
	// // if (current == ACTIVITY_NOTHING) {
	// //
	// // // 遷移元画面番号が取得できない
	// // // --スタート画面に遷移する
	// //
	// // onErrorStartActivity(new RuntimeException("遷移先画面がありません"), l,
	// // logNo);
	// //
	// // return;
	// // }
	//
	// // 遷移先画面番号
	// final int next = getNextActivityNo(current);
	// if (next == ACTIVITY_NOTHING) {
	//
	// // --------- NG --------------- start
	//
	// // 遷移先画面が取得できなかった
	// // -- スタート画面に遷移する
	// // moveToFirstActivity(l);
	// // --------- NG --------------- finish
	//
	// onErrorStartActivity(new RuntimeException("遷移先画面がありません"),
	// l, logNo);
	//
	// return;
	// }
	//
	// // 画面遷移
	// startActivity(next, l, isFragment, logNo);
	//
	// }
	// }, l);
	//
	// }

	/**
	 * 前の画面に遷移する
	 *
	 * @param cls
	 *            遷移元画面
	 * @param l
	 *            リスナー
	 */
	// @Override
	// public void moveToPreviousActivity(final Object cls,
	// final ControllerListener l, final int logNo) {
	// moveToPreviousActivity(cls, l, false, logNo);
	// }
	//
	// public void moveToPreviousActivity(final Object cls,
	// final ControllerListener l, final boolean isFragment,
	// final int logNo) {
	//
	// mMessagingController.putCommand("move to previous", new Runnable() {
	//
	// @Override
	// public void run() {
	//
	// // 遷移元画面番号
	// final int current = getActivityNo(cls);
	//
	// if (current == ACTIVITY_NOTHING) {
	// // 遷移元画面番号が取得できなかった
	// // --> スタート画面
	// // moveToFirstActivity(l, logNo);
	//
	// onErrorStartActivity(new RuntimeException("遷移先画面がありません"),
	// l, logNo);
	//
	// // 処理終了
	// return;
	// }
	//
	// // 遷移先画面番号
	// final int next = getPreviousActivityNo(current);
	//
	// if (next == ACTIVITY_NOTHING) {
	// // 遷移先画面番号が取得できなかった
	// // --> モード終了
	//
	// // finishMode(l, logNo);
	//
	// onErrorStartActivity(new RuntimeException("遷移先画面がありません"),
	// l, logNo);
	// // 処理終了
	// return;
	// }
	//
	// // 画面起動
	// startActivity(next, l, isFragment, logNo);
	//
	// }
	// }, l);
	//
	// }

	/**
	 * 画面起動前にチェックを行う場合はオーバーライドする
	 *
	 * @param className
	 * @param l
	 * @param logNo
	 * @return
	 */
	// protected boolean dispachStartActivity(final String className,
	// final ControllerListener l, final int logNo) {
	// return true;
	// }

	/**
	 * 画面起動
	 *
	 * @param no
	 *            起動画面
	 * @param l
	 *            リスナー
	 * @param logNo
	 */
	private void startActivity(final int no, final ControllerListener l,
			final boolean isFragment, final int logNo) {

//		CLog.dt(this, logNo, "=============  指定された画面を起動する 画面番号 = " + no);

		// 遷移先画面クラス名を取得する
		final String cls = isFragment
				? getFragmentName(no, logNo)
				: getActivityName(no, logNo);

//		CLog.dt(this, logNo, "指定された画面を起動する 画面クラス名 = " + cls);

		// if (!dispachStartActivity(cls, l, logNo)) {
		//
		// CLog.dt(this, logNo, "画面起動前チェックで起動が中断されました");
		//
		// onErrorStartActivity(null, l, logNo);
		//
		// return;
		// }

		if (cls != null) {

			// 遷移用 Intent 作成
			final Intent intent = new Intent();
			// -- パッケージ名を設定
			intent.setPackage(getApplication(logNo).getPackageName());
			// -- コンポーネント名（画面クラス名）を設定
			if (isFragment) {
				intent.setComponent(new ComponentName(getApplication(logNo),
						getFragmentActivityName(logNo)));
				intent.putExtra(AbstractActivity.EXTRA_FRAGMENT_CLASS_NAME, cls);
			} else {
				intent.setComponent(new ComponentName(getApplication(logNo),
						cls));
			}
			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
			// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

			try {

//				CLog.dt(this, logNo, "startActivity 画面起動指示！！ ");

				// 画面を起動する
				getApplication(logNo).startActivity(intent);

//				CLog.dt(this, logNo, "画面起動指示をしたので画面起動イベントを発行する");

				// 画面起動イベントを発行する
				onStartActivity(no, l, logNo);

			} catch (final Exception e) {

				// 画面起動失敗
//				CLog.e(this, 0, "画面の起動時に例外が発生しました", e);

				// 画面起動失敗イベント発行
				onErrorStartActivity(no, e, l, logNo);
			}
		} else {

			onErrorStartActivity(no, new RuntimeException("遷移先画面がありません"), l,
					logNo);
			// throw new IllegalArgumentException("遷移先画面クラス名が指定されていない");
		}

	}

	/**
	 * 画面起動イベントを発行する
	 *
	 * @param l
	 *            追加リスナー
	 */
	private void onStartActivity(final int activityNo,
			final ControllerListener l, final int logNo) {

//		CLog.dt(this, logNo, "画面起動イベントを発行する");

		// 別スレッドとして実行
		new Thread(new Runnable() {

			@Override
			public void run() {

				// リスナーセットを取得
				final Set<ControllerListener> ls = getMessagingListeners(l,
						logNo);
				// -- Iterator 取得
				final Iterator<ControllerListener> it = ls.iterator();

				// -- イベント発行
				while (it.hasNext()) {
					// 画面起動イベントを発行する
					try {
						it.next().onStartActivity(activityNo);
					} catch (final Exception e) {
						// TODO : 例外が発生した時に何か処理をするか検討
//						CLog.d(this, 0, "画面起動イベント発行で例外が発生しました", e);
						e.printStackTrace();

					}
				}
//				CLog.dt(this, logNo, "画面起動イベント発行完了");
			}
		}).start(); // スレッド開始

	}

	/**
	 * 画面起動失敗イベントを発行する
	 *
	 * @param l
	 *            追加リスナー
	 */
	private void onErrorStartActivity(final int activityNo, final Throwable tr,
			final ControllerListener l, final int logNo) {

//		CLog.dt(this, logNo, "onStartActivity()");

		// 別スレッドとして実行
		new Thread(new Runnable() {

			@Override
			public void run() {

				// リスナーセットを取得
				final Set<ControllerListener> ls = getMessagingListeners(l,
						logNo);
				// -- Iterator 取得
				final Iterator<ControllerListener> it = ls.iterator();

				// -- イベント発行
				while (it.hasNext()) {
					// 画面起動イベントを発行する
					try {
						it.next().onErrorStartActivity(activityNo, tr);
					} catch (final Exception e) {
						// TODO : 例外が発生した時に何か処理をするか検討
//						CLog.d(this, 0, "画面起動イベント発行で例外が発生しました", e);
						e.printStackTrace();

					}
				}
			}
		}).start(); // スレッド開始

	}

	@Override
	public void sendOnCreateMessage(final Object obj) {

		// コマンド
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {
				final Pair<Integer, Boolean> value = getObjectNo(obj);

				if (value == null) {
					return;
				}

				// モード番号
				final int mode = getMode(-1);

				final Set<ControllerListener> listeners = getMessagingListeners(-1);
				final Iterator<ControllerListener> it = listeners.iterator();
				while (it.hasNext()) {

					try {

						it.next().onCreate(mode, value.first, value.second);

					} catch (final Exception e) {
						e.printStackTrace();
					}

				}

			}
		};
		getMessagingController(-1).putCommand("event", runnable, false,
				new ControllerListener());

	}

	@Override
	public void sendOnResumeMessage(final Object obj) {
		// コマンド
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {
				final Pair<Integer, Boolean> value = getObjectNo(obj);

				if (value == null) {
					return;
				}

				// モード番号
				final int mode = getMode(-1);

				final Set<ControllerListener> listeners = getMessagingListeners(-1);
				final Iterator<ControllerListener> it = listeners.iterator();
				while (it.hasNext()) {

					try {

						it.next().onResume(mode, value.first, value.second);

					} catch (final Exception e) {
						e.printStackTrace();
					}

				}

			}
		};
		getMessagingController(-1).putCommand("event", runnable, false,
				new ControllerListener());

	}

	@Override
	public void sendOnStopMessage(final Object obj) {
		// コマンド
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {
				final Pair<Integer, Boolean> value = getObjectNo(obj);

				if (value == null) {
					return;
				}

				// モード番号
				final int mode = getMode(-1);

				final Set<ControllerListener> listeners = getMessagingListeners(-1);
				final Iterator<ControllerListener> it = listeners.iterator();
				while (it.hasNext()) {

					try {

						it.next().onStop(mode, value.first, value.second);

					} catch (final Exception e) {
						e.printStackTrace();
					}

				}

			}
		};
		getMessagingController(-1).putCommand("event", runnable, false,
				new ControllerListener());

	}

	@Override
	public void sendOnDestroyMessage(final Object obj) {

		// コマンド
		final Runnable runnable = new Runnable() {

			@Override
			public void run() {
				final Pair<Integer, Boolean> value = getObjectNo(obj);

				if (value == null) {
					return;
				}

				// モード番号
				final int mode = getMode(-1);

				final Set<ControllerListener> listeners = getMessagingListeners(-1);
				final Iterator<ControllerListener> it = listeners.iterator();
				while (it.hasNext()) {

					try {

						it.next().onDestroy(mode, value.first, value.second);

					} catch (final Exception e) {
						e.printStackTrace();
					}

				}

			}
		};
		getMessagingController(-1).putCommand("event", runnable, false,
				new ControllerListener());
	}

	/**
	 * 画面番号取得
	 *
	 * @param obj
	 *            取得対象
	 * @return <画面番号、アクティビティフラグ>
	 */
	private Pair<Integer, Boolean> getObjectNo(final Object obj) {
		// パラメータチェック
		if (obj == null) {
			return null;
		}
		// 画面番号
		final int no = getNo(obj);
		if (no == INVALID_VALUE) {
			return null;
		}

		final String name = getActivityName(no, -1);
		final boolean isActivity = (name != null);

		return new Pair<Integer, Boolean>(no, isActivity);

	}

}
